<?php

namespace CMBUBank\Common\Encrypt;

use Exception;
use FG\ASN1\ASNObject;
use FG\ASN1\Exception\ParserException;
use Mdanter\Ecc\Crypto\Signature\Signature;
use Mdanter\Ecc\Serializer\Signature\DerSignatureSerializer;
use Rtgm\sm\RtSm2;
use Rtgm\sm\RtSm4;

/***
 * sm2 sm4 (招商银行云直联) 签名
 */
class SM4Encrypt
{
    private $vi;

    private $privateKey;

    private $sm4Key;

    private $bankPublicKey;

    /**
     * RtSm4
     * @var RtSm4 $RtSm4
     */
    private $RtSm4;

    /**
     * RtSm2
     * @var RtSm2 $RtSm2
     */
    private $RtSm2;

    public function __construct(string $privateKey, string $sm4Key, string $vi, string $bankPublicKey)
    {
        $this->vi = $vi;
        $this->sm4Key = $sm4Key;
        $this->privateKey = $privateKey;
        $this->bankPublicKey = $bankPublicKey;

        if ($this->sm4Key) {
            $this->RtSm4 = new RtSm4($this->sm4Key);
            $this->RtSm2 = new RtSm2('base64');
        }
    }

    /**
     * 签名
     * @param string $string
     * @return string
     * @throws ParserException
     */
    public function signature(string $string): string
    {
        $key = bin2hex(base64_decode($this->privateKey));

        $sign = $this->RtSm2->doSign($string, $key, $this->vi);

        $sign = base64_decode($sign);
        $a = ASNObject::fromBinary($sign)->getChildren();

        $aa = self::formatHex($a[0]->getContent());
        $bb = self::formatHex($a[1]->getContent());
        $sign = $aa . $bb;
        return base64_encode(hex2bin($sign));
    }

    private function formatHex($dec)
    {
        $hex = gmp_strval(gmp_init($dec, 10), 16);
        $len = strlen($hex);
        if ($len == 64) {
            return $hex;
        }
        if ($len < 64) {
            $hex = str_pad($hex, 64, "0", STR_PAD_LEFT);
        } else {
            $hex = substr($hex, $len - 64, 64);
        }

        return $hex;
    }

    /**
     * 加密
     * @param string $string
     * @return string
     * @throws Exception
     */
    public function encrypt(string $string): string
    {
        return $this->RtSm4->encrypt($string, 'sm4', $this->vi, 'base64');
    }

    /**
     * 解密
     * @param string $string
     * @return false|string
     * @throws Exception
     */
    public function decrypt(string $string): string
    {
        return $this->RtSm4->decrypt($string, 'sm4-cbc', $this->vi, 'base64');
    }

    /**
     * 验签
     * @param $sign
     * @param $data
     * @return void
     */
    public function verifySign($sign, $data):bool
    {
        $signHex = bin2hex(base64_decode($sign));

        $r = substr($signHex, 0, 64);
        $s = substr($signHex, 64, 64);

        $r = gmp_init($r, 16);
        $s = gmp_init($s, 16);

        $signature = new Signature($r, $s);

        $serializer = new DerSignatureSerializer();
        $serializedSig = $serializer->serialize($signature);

        $sign = base64_encode($serializedSig);

        $pub = bin2hex(base64_decode($this->bankPublicKey));

        return $this->RtSm2->verifySign($data, $sign, $pub, $this->vi);
    }
}
